第十一章:JavaScript 表單事件監聽與資料處理
在前端開發中,表單驗證與資料的捕捉是非常重要的環節。透過 jQuery,我們可以輕鬆監聽使用者的輸入狀態,並決定是否允許表單送出。
1. 監聽 Form 內容與基礎驗證
A. 狀態旗標 (Flags) 與資料準備
在進行表單驗證時,我們通常會在全域宣告一組布林值(Boolean)變數作為「旗標」。只有當所有欄位都驗證成功(轉為 true),才允許表單送出。
// 宣告公有變數 (預設為 false,代表尚未通過驗證)
let flag_username01 = false;
let flag_password01 = false;
let flag_email01 = false;
// 模擬後端傳來的 JSON 陣列資料 (後續可用於渲染下拉選單)
const foodData = [
{
"name": "牛肉麵",
"price": 150,
"description": "經典紅燒湯頭搭配燉煮入味的牛肉與Q彈麵條。"
},
{
"name": "雞排飯",
"price": 120,
"description": "香酥雞排搭配白飯與清爽配菜。"
}
];
B. 表單送出監聽 (Submit) 與阻擋預設行為
傳統的 <form> 送出時會造成整個網頁重新整理。為了使用 AJAX 在背景傳遞資料,我們必須先阻擋這個預設行為。
$(function () {
// 監聽 form 的 submit 事件
$("#myForm").submit(function (e) {
// 判斷所有欄位的旗標是否都為 true
if (flag_username01 == true && flag_password01 == true && flag_email01 == true) {
e.preventDefault(); // 阻擋表單預設的網頁重整行為
console.log("form 觸發成功!");
// 透過 .val() 提取使用者在 input 欄位中輸入的值
console.log($("#username01").val());
console.log($("#password01").val());
console.log($("#email01").val());
// 這裡可以接續寫 AJAX 程式碼將資料送往後端...
} else {
e.preventDefault(); // 即便驗證失敗,也要阻擋重整
alert("欄位有錯誤, 請修正!");
}
});
});
e.preventDefault()這是在處理表單送出(Submit)或超連結點擊(Click)時必備的指令,作用是「阻止瀏覽器執行該元素的預設動作」。
C. 狀態改變事件:Change vs Blur
在即時驗證使用者輸入的內容時,我們常使用以下兩種觸發時機:
change(內容改變): 當使用者在欄位內輸入內容,且游標離開該欄位(失去焦點),系統偵測到內容與一開始不同時才會觸發。blur(失去焦點): 不管使用者有沒有修改內容,只要游標一離開該欄位就會立刻觸發。常應用於「必填欄位防呆」,提醒使用者忘記填寫。
// 觸發條件:數值有變更 + 游標失去焦點
$("#password01").change(function () {
console.log("密碼已變更:" + $("#password01").val());
});
// 觸發條件:單純游標失去焦點 (即刻觸發)
$("#email01").blur(function () {
console.log("Email 欄位失去焦點:" + $("#email01").val());
});
D. 實戰範例:結合 Bootstrap 驗證類別與字數判斷
這是一個完整的欄位防呆應用。當使用者輸入完帳號並移開游標時(觸發 blur
事件),程式會立刻檢查字數是否合乎規定,並給予即時的視覺回饋(綠色勾勾或紅色叉叉),同時更新對應的過關旗標。
$("#username01").blur(function () {
// 1. 取得當前輸入的字數
// $(this) 代表觸發此事件的元素本身 (#username01)
console.log("目前字數:" + $(this).val().length);
// 2. 邏輯判斷:字數大於 0 且小於 7 (亦即 1~6 個字)
if ($(this).val().length > 0 && $(this).val().length < 7) {
// 符合規定:移除紅框,加上綠框 (Bootstrap 樣式)
$(this).removeClass("is-invalid").addClass("is-valid");
// 將該欄位的全域過關旗標設為 true
flag_username01 = true;
} else {
// 不符合規定:移除綠框,加上紅框
$(this).removeClass("is-valid").addClass("is-invalid");
// 將過關旗標設為 false,未來表單將無法送出
flag_username01 = false;
}
});
$(this)的妙用: 在 jQuery 事件中,$(this)自動指向當下正在操作的元素。這能避免一直重複寫$("#username01"),讓程式碼更簡潔、維護性更高。- 長度判斷: 利用
.val().length抓取輸入字串的長度,作為if...else的判斷條件。 - 動態切換樣式: 透過
removeClass()與addClass()的組合技,動態抽換 Bootstrap 的驗證類別。is-valid會使框線變綠並顯示下方的.valid-feedback文字;is-invalid則使框線變紅並顯示.invalid-feedback文字。
2. 表單樣式 (不使用 form) 與 AJAX 送出準備
在現代前端開發中,我們常會省略 <form> 標籤,直接使用一般的 <button type="button">
來觸發資料送出。這樣做的好處是完全不需要處理網頁重新整理的問題 (不需寫 e.preventDefault()),非常適合搭配 AJAX 進行前後端分離的資料傳輸。
A. 實作邏輯與程式碼
以下範例為三個獨立欄位分別綁定 blur 事件進行即時驗證,最後透過一個獨立的按鈕點擊 (click) 來統整狀態並取得資料:
// 1. 針對各別欄位設定獨立的 blur (失去焦點) 驗證
$("#username02").blur(function () {
// 帳號長度限制:1~6 個字
if ($(this).val().length > 0 && $(this).val().length < 7) {
$(this).removeClass("is-invalid").addClass("is-valid");
flag_username02 = true;
} else {
$(this).removeClass("is-valid").addClass("is-invalid");
flag_username02 = false;
}
});
$("#password02").blur(function () {
// 密碼長度限制:2~8 個字
if ($(this).val().length > 1 && $(this).val().length < 9) {
$(this).removeClass("is-invalid").addClass("is-valid");
flag_password02 = true;
} else {
$(this).removeClass("is-valid").addClass("is-invalid");
flag_password02 = false;
}
});
$("#email02").blur(function () {
// Email長度限制:2~10 個字
if ($(this).val().length > 1 && $(this).val().length < 11) {
$(this).removeClass("is-invalid").addClass("is-valid");
flag_email02 = true;
} else {
$(this).removeClass("is-valid").addClass("is-invalid");
flag_email02 = false;
}
});
// 2. 監聽一般按鈕的點擊事件 (而非 form 的 submit)
$("#btn02").click(function () {
// 檢查所有旗標是否皆為 true
if (flag_username02 && flag_password02 && flag_email02) {
console.log("按鈕點擊驗證通過,準備執行 AJAX!");
// 蒐集所有資料準備打包給後端
console.log($("#username02").val());
console.log($("#password02").val());
console.log($("#email02").val());
} else {
// 只要有一個旗標為 false,就擋下並提示使用者
alert("欄位錯誤, 請修正!");
}
});
B. 核心觀念解析
- 個別欄位驗證 (Modularity): 將每個欄位的驗證邏輯拆開獨立處理,不僅讓程式碼更容易閱讀,未來若要修改單一欄位(如:帳號)的規則,也不會影響到其他欄位。
if (flag_A && flag_B)縮寫寫法: 在 JavaScript 中,如果變數本身就是布林值 (Boolean),在if判斷式內可以省略== true,直接寫變數名稱即可,這是一種更簡潔俐落的寫法。- 取代 Submit 的 Click 事件: 因為沒有
<form>,我們改用$("#btn02").click(...)來攔截使用者的送出意圖。在這個區塊內將所有.val()蒐集起來打包成 JSON 格式,就是前端串接後端 API (如 Python Flask) 的標準前置作業。
3. 動態渲染下拉選單 (Data Rendering)
在實務開發中,下拉選單的選項往往不是寫死的,而是從後端資料庫或 API 取得。透過 jQuery 的 empty() 與 append()
方法,我們可以輕鬆地將資料陣列轉換成網頁上的選項。
A. 渲染邏輯三步驟
- 清空舊資料: 使用
.empty()移除選單內原有的所有<option>,避免新舊資料重複堆疊。 - 重置預設選項: 重新插入一筆帶有提示文字(如:***請選擇餐點***)且被
disabled的選項。 - 迴圈跑資料: 使用 JavaScript 的
forEach遍歷資料陣列,並將每一筆資料拼接到 HTML 字串中後append進選單。
// 渲染餐點選單實作
$("#myfood").empty(); // 步驟 1:清空
// 步驟 2:重置預設提示
$("#myfood").append(`<option value="" class="text-center" disabled selected>***請選擇餐點***</option>`);
// 步驟 3:遍歷陣列並動態渲染
foodData.forEach(function (item) {
// 利用樣板字面值 (Template Literals) 拼接 HTML
let strHTML = `<option value="${item.name}">${item.name}</option>`;
$("#myfood").append(strHTML);
});
B. 核心語法解析
- 樣板字面值 (Backticks): 使用反引號(
`)來撰寫字串。這讓我們可以直接在字串中換行,並透過${變數}的語法輕鬆嵌入資料,取代傳統瑣碎的+號拼接。 forEach迴圈: 這是處理陣列資料的標準方法。它會針對foodData內的每一筆物件執行一次函式,並將當下的資料賦予給參數(例如item)。- 值變更監聽 (Change Event):
當使用者從下拉選單選擇了不同項目後,會觸發
change事件。這通常用來即時取得使用者選取的值($(this).val()),以便進行後續運算或篩選。
// 監聽選單變更事件
$("#mycity").change(function () {
// 當城市選單變更時,即時印出選取的值
console.log("當前選擇的城市:" + $(this).val());
});
$("#myfood").change(function () {
// 當餐點選單變更時,即時印出選取的值
console.log("當前選擇的餐點:" + $(this).val());
});
在執行
append() 之前,務必記得先執行 empty()。如果少了清空的步驟,每次執行渲染函式時,選單的選項就會無限增加。
4. 滑桿用法與即時數值連動 (Range Input)
在使用 <input type="range"> 時,最核心的互動需求就是讓使用者在拖曳滑桿時,畫面上的數字能「同步」更新。這需要透過監聽
input 事件來達成。
A. 事件監聽:input vs. change
input事件 (即時): 當使用者只要一移動滑桿,數值就會立刻傳回並觸發。這是做出「絲滑」互動的首選。change事件 (放開): 必須等到使用者拖曳完畢、放開滑鼠的那一刻才會觸發。
B. 實作邏輯與程式碼
假設 HTML 結構中有一個 ID 為 #num 的滑桿,以及一個用來顯示數字的 ID #num_text:
// 監聽滑桿的 input 事件
$("#num").on("input", function () {
// 1. 在主控台印出目前滑桿的數值
console.log($(this).val());
// 2. 將滑桿目前的數值 (.val()) 寫入到顯示文字的標籤 (.text()) 中
$("#num_text").text($(this).val());
});
C. 重點解析
.on("input", function(){...}): 這是 jQuery 綁定事件的標準寫法。使用on的擴充性較好,能同時監聽多個事件。$(this).val():$(this)指向目前被操作的滑桿本身,透過.val()抓取它當下的數值(通常是 0~100 之間的字串)。- UI 同步: 透過
$("#num_text").text(...),我們將變動的數值即時渲染到 HTML 畫面上,讓使用者在操作時能得到立即的視覺回饋。
在處理大量數據或繪圖(如地圖縮放、Canvas 繪圖)時,若
input 觸發過於頻繁導致效能下降,開發者有時會改用 change
或是加上防抖(Debounce)機制。但在一般表單數值顯示中,使用 input 效果最佳。
5. 單選 (Radio) 與 複選 (Checkbox) 的資料取值
在處理多選或單選元件時,我們不能只靠單一的 ID 取值,因為它們通常是一組具備相同 name 屬性的集合。關鍵在於利用 jQuery 的
:checked 選擇器來過濾出「被勾選」的項目。
A. 複選框 (Checkbox) 的多重取值邏輯
由於複選框允許選取多個項目,我們通常會準備一個空的「陣列 (Array)」來儲存結果,並搭配 each() 迴圈來遍歷所有被勾選的框。
// 1. 設定初始值 (在網頁載入時預先勾選)
$("input[name='sport'][value='跑步']").prop("checked", true);
// 2. 監聽確認按鈕
$("#btn06").click(function () {
let result = []; // 準備一個空陣列來存放選中的運動
// 使用 :checked 選擇器抓取所有被勾選的 sport
$("input[name='sport']:checked").each(function () {
// $(this) 代表當下這一個被勾選的 input
console.log("選中值:" + $(this).val());
result.push($(this).val()); // 將值存入陣列中
});
// 3. 簡單防呆驗證
if (result.length == 0) {
alert("至少選擇一個運動!");
} else {
console.log("最終勾選清單:", result);
}
});
B. 單選框 (Radio) 的取值方法
單選框雖然在同一組 name 中只能選一個,但取值方式與複選框類似。使用 :checked 能確保我們抓到的是目前選中的那一筆。
// 1. 設定初始預設選中項
$("input[name='edu'][value = '高中職']").prop("checked", true);
// 2. 取出所選取的值
$("#btn07").click(function () {
// 雖然單選只有一個值,但同樣可以使用 :checked 選擇器
$("input[name='edu']:checked").each(function () {
console.log("教育程度:" + $(this).val());
});
});
C. 重點語法解析
.prop("checked", true): 這是操作元素屬性(Properties)的最佳方式。相較於attr(),prop()更適合處理如 checked、disabled、selected 等具備布林值的狀態。:checked選擇器: 這是 jQuery 專門用來過濾「選取狀態」的偽類選擇器。它能讓我們只對有被勾選的元件進行操作。result.push(): JavaScript 陣列的標準方法,用來將新資料加入陣列的末端。在處理複選資料時,這能幫我們把分散的 input 值整理成一份乾淨的清單。.each()迴圈: 當 jQuery 選擇器選到多個元素時,使用each()可以逐一對這些元素執行指定的函式。